package Algorithms;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;


public class LinearRegressionCrossValidation extends
		LinearRegressionWithRegularization {
	
	/**
	 * Maximum value of lambda
	 */
	private double lambdaLimit = 5;
	
	private double lambdaIncrement = 0.5;
			
	private double lambdaStart = 0.1;
	/**
	 * Number of fold for the k-fold cross validation.
	 */
	private double k = 10;
	Map<Double,LinearRegressionWithRegularization> models = new 
			HashMap<Double,LinearRegressionWithRegularization>();
	

	public LinearRegressionCrossValidation(double[][] x, double[] y, int params) {
		super(x, y, params);
		
	}
	

	public LinearRegressionCrossValidation(List<List<Double>> x, double[] y,
			int params, int dim) {
		super(x, y, params, dim);
	}

	
	
	public void getModels(){
        for(double i = 0.1; i<=lambdaLimit;i = i*2){
        	this.setLambda(i);
        	this.gradientDescent();
        	models.put(i, this);       	
        }
	}
	
	
	public double getCrossValidationError(){
		
		Map<double[][],double[]> splits = new ConcurrentHashMap<>();
		double[][] x = this.x;
		double[][] xs;
		double[] 	ys ;
		int length = x[0].length;
		int size = (int) Math.floor(x[0].length / k);
		for(int i=0; i < length ; i +=size){
			if((i + (2*size)) >= length + 1){
				xs = splitMatrix(x,i,length - i);
				ys = splitArray(this.y, i, length - i);

			}else{
				xs = splitMatrix(x,i,size);
				ys = splitArray(this.y,i,size);
			}
			splits.put(xs, ys);
		}

	
		double error = Double.MAX_VALUE;
		double limite = k;
		double prueba = 0;
		for(double[][] k : splits.keySet()){
			double[][] crossValidationSet = k;
			double[] crossValidationY = splits.get(k);
			Set<double[][]> stub = splits.keySet();
			stub.remove(k);
			double[][] trainingSet = null;
			double[] trainingOutput = null;
			for(double[][] s : stub){
				if(trainingSet == null){
					trainingSet = s;
					trainingOutput = splits.get(s);
				}else{
					trainingSet = linkMatrix(trainingSet,s);
					trainingOutput = linkArray(trainingOutput,splits.get(s));
				}	
			}
			prueba++;
			if(prueba < limite){
			double partialError = getPartialError(crossValidationSet,crossValidationY,trainingSet,trainingOutput);
			System.out.println("Estoy calculando un error parcial");
			error = error + partialError;}
		}
		return error/(k-1);	
		
	}



	private double[] linkArray(double[] trainingOutput, double[] ds) {
		double[] result = new double[trainingOutput.length + ds.length];
		for(int i = 0; i < result.length; i++){
			if(i < trainingOutput.length)
				result[i] = trainingOutput[i];
			else
				result[i] = ds[i-trainingOutput.length];
		}
		
		return result;
		
	}


	private double[][] linkMatrix(double[][] trainingSet, double[][] s) {
		double[][] result = new double[trainingSet.length][trainingSet[0].length + s[0].length];
		for(int i = 0; i < result[0].length;i++){
			for(int p = 0; p < result.length; p++){
				if(i < trainingSet[0].length){
					result[p][i] = trainingSet[p][i];
				}else
					result[p][i] = s[p][i-trainingSet[0].length];
			}
		}
		
		
		return result;
	}


	private double[] splitArray(double[] y, int i, int j) {
		
		double[] result = new double[j];
		int p = 0;
		int stop = j + i;
		while(i<stop){
			result[p] = y[i];
			p++;
			i++;
		}
		return result;

	}


	private double[][] splitMatrix(double[][] x, int i, int j) {
		double[][] result = new double[x.length][j];
		int q = 0;
		int stop = j + i;
		while(i<stop){
			for(int p = 0; p < x.length ; p++ ){
					result[p][q] = x[p][i];
				}
			q++;
			i++;
		}
		return result;
	}



	private double getPartialError(double[][] crossValidationSet,
			double[] crossValidationY, double[][] trainedSet,
			double[] trainedOutput) {
		
		double partialError = Double.MAX_VALUE;
		LinearRegressionWithRegularization train = new LinearRegressionWithRegularization(trainedSet,
				trainedOutput,this.params.length);
		for(double i = lambdaStart; i<lambdaLimit;i = i + lambdaIncrement){
			train.setLambda(i);
			train.gradientDescent();
			double lambdaerror = 0;
			for(int i1 = 0; i1<crossValidationY.length; i1++){
				lambdaerror += Math.pow((train.getFunction((int) crossValidationSet[0][i1]) - y[i1]),2);
			}
			lambdaerror = lambdaerror / (2*y.length);
			if(lambdaerror<partialError)
				partialError = lambdaerror;
		}

		return partialError;
	}


	public LinearRegressionWithRegularization getOptimun(){
		double error = getCrossValidationError();
		double diference = Double.MAX_VALUE;
		LinearRegressionWithRegularization result = null;
		for(double i = lambdaStart; i<lambdaLimit;i = lambdaIncrement+ i){
			this.setLambda(i);
			this.gradientDescent();
			double tError = this.getErrors()[this.getErrors().length - 1];
			double tDiference = tError - error;
				if(tDiference < diference){
					result = this;				
				}

		}
		return result;
	}
	
}
